package com.stone.redis.lock.infra.lock.impl;

import com.stone.redis.lock.domain.bo.LockInfo;
import com.stone.redis.lock.infra.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;

/**
 * 可重入redis分布式锁实现
 *
 * @author Mr_wenpan@163.com 2021/07/23 22:47
 */
@Slf4j
@Component
public class ReentrantRedisLock implements RedisLock {

    private static final DefaultRedisScript<Long> LOCK_SCRIPT;

    private static final DefaultRedisScript<Object> UNLOCK_SCRIPT;

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 锁信息
     */
    private LockInfo lockInfo;

    public ReentrantRedisLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    static {
        // 加载获取锁的脚本
        LOCK_SCRIPT = new DefaultRedisScript<>();
        LOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("script.lua/lock.lua")));
        LOCK_SCRIPT.setResultType(Long.class);

        // 加载释放锁的脚本
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("script.lua/unlock.lua")));
    }

    @Override
    public boolean tryLock() {
        if (Objects.isNull(lockInfo)) {
            log.warn("请设置锁信息再获取锁.");
            return false;
        }
        if (Objects.isNull(lockInfo.getLeaseTime())) {
            log.error("必须要设置锁的过期释放时间，以免造成死锁.");
            return false;
        }
        // 设置获取锁的线程的唯一标识，便于后面重入解锁
        lockInfo.setThreadUniqueIdentifier(UUID.randomUUID().toString() + Thread.currentThread().getId());
        Long result = stringRedisTemplate.execute(LOCK_SCRIPT, Collections.singletonList(lockInfo.getName()),
                lockInfo.getThreadUniqueIdentifier(), String.valueOf(lockInfo.getLeaseTime()));
        System.out.println("result=" + result);
        return result != null && result.intValue() == 1;
    }

    @Override
    public void unlock() {
        log.info("释放锁：{}，线程唯一ID：{}", lockInfo.getName(), lockInfo.getThreadUniqueIdentifier());
        stringRedisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockInfo.getName()),
                lockInfo.getThreadUniqueIdentifier());
    }

    public void setLockInfo(LockInfo lockInfo) {
        this.lockInfo = lockInfo;
    }
}
